{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Word2Vec笔记\n",
    "\n",
    "学习word2vec的skip-gram实现，除了skip-gram模型还有CBOW模型。\n",
    "Skip-gram模式是根据中间词，预测前后词，CBOW模型刚好相反，根据前后的词，预测中间词。\n",
    "\n",
    "那么什么是**中间词**呢？什么样的词才叫做**前后词**呢？\n",
    "\n",
    "首先，我们需要定义一个窗口大小，在窗口里面的词，我们才有中间词和前后词的定义。一般这个窗口大小在5-10之间。\n",
    "举个例子，我们设置窗口大小（window size）为2：\n",
    "```bash\n",
    "|The|quick|brown|fox|jump|\n",
    "```\n",
    "那么，`brown`就是我们的中间词，`The`、`quick`、`fox`、`jump`就是前后词。\n",
    "\n",
    "我们知道，word2vec实际上就是一个神经网络（后面会解释），那么这样的数据，我们是以什么样的格式用来训练的呢？\n",
    "看一张图，你就明白了：\n",
    "\n",
    "![word2vec window](images/word2vec_window.png)\n",
    "\n",
    "可以看到，我们总是以**中间词**放在第一个位置，然后跟着我们的前后相邻词。可以看到，每一对词都是一个输入和一个输出组成的数据对(X,Y)。其中，X是feature，Y是label。\n",
    "\n",
    "所以，我们训练模型之前，需要根据语料，整理出所有的像上面这样的输入数据用来训练。\n",
    "\n",
    "## word2vec是一个神经网络\n",
    "\n",
    "word2vec是一个简单的神经网络，有以下几个层组成：\n",
    "\n",
    "* １个输入层\n",
    "* 1个隐藏层\n",
    "* 1个输出层\n",
    "\n",
    "输入层输入的就是上面我们说的数据对的数字表示，输出到隐藏层。\n",
    "隐藏层的神经网络单元的数量，其实就是我们所说的**embedding size**，只有为什么，我们后面简单计算一下就知道。需要注意的是，我们的隐藏层后面不需要使用激活函数。\n",
    "输出层，我们使用softmax操作，得到每一个预测结果的概率。\n",
    "\n",
    "这里有一张图，能够表示这个网络：\n",
    "![skip-gram-net-arhc](images/skip_gram_net_arch.png)\n",
    "\n",
    "### 输入层\n",
    "现在问题来了，刚刚我们说，输入层的输入是我们之前准备的数据对的数字表示，那么我们该如何用数字表示文本数据呢？\n",
    "\n",
    "好像随便一种方式都可以用来表示我们的文本啊。\n",
    "\n",
    "看上图，我们发现，它的输入使用的是**one-hot**编码。什么是ont-hot编码呢？如图所示，假设有n个词，则每一个词可以用一个n维的向量来表示，这个n维向量只有一个位置是1，其余位置都是0。\n",
    "\n",
    "那么为什么要用这样的编码来表示呢？答案后面揭晓。\n",
    "\n",
    "\n",
    "### 隐藏层\n",
    "隐藏层的神经单元数量，代表着每一个词用向量表示的维度大小。假设我们的**hidden_size**取300，也就是我们的隐藏层有300个神经元，那么对于每一个词，我们的向量表示就是一个$1*N$的向量。\n",
    "有多少个词，就有多少个这样的向量！\n",
    "\n",
    "所以对于**输入层**和**隐藏层**之间的权值矩阵$W$，它的形状应该是`[vocab_size, hidden_size]`的矩阵，\n",
    "\n",
    "### 输出层\n",
    "那么我们的输出层，应该是什么样子的呢？从上面的图上可以看出来，输出层是一个`[vocab_size]`大小的向量，每一个值代表着输出一个词的概率。\n",
    "\n",
    "为什么要这样输出？因为我们想要知道，对于一个输入词，它接下来的词最有可能的若干个词是哪些，换句话说，我们需要知道它接下来的词的概率分布。\n",
    "\n",
    "你可以再看一看上面那张网络结构图。\n",
    "\n",
    "你会看到一个很常见的函数**softmax**，为什么是softmax而不是其他函数呢？不妨先看一下softmax函数长啥样：\n",
    "\n",
    "$$ softmax(x) = \\frac{e^x}{\\sum_{i}^{N}e^{x_i}}$$\n",
    "\n",
    "很显然，它的取值范围在(0,1)，并别所有的值和为1。这不就是天然的概率表示吗？\n",
    "\n",
    "当然，softmax还有一个性质，因为它函数指数操作，如果**损失函数使用对数函数**，那么可以抵消掉指数计算。\n",
    "\n",
    "关于更多的softmax，请看斯坦福[Softmax Regression](http://ufldl.stanford.edu/tutorial/supervised/SoftmaxRegression/)\n",
    "\n",
    "\n",
    "### 整个过程的数学表示\n",
    "至此，我们已经知道了整个神经网络的结构，那么我们应该怎么用数学表示出来呢？\n",
    "\n",
    "回顾一下我们的结构图，很显然，三个层之间会有两个权值矩阵$W$，同时，两个偏置项$b$。所以我们的整个网络的构建，可以用下面的伪代码：\n",
    "\n",
    "```python\n",
    "import tensorflow as tf\n",
    "\n",
    "# 假设vocab_size = 1000\n",
    "VOCAB_SIZE = 1000\n",
    "# 假设embedding_size = 300\n",
    "EMBEDDINGS_SIZE = 300\n",
    "\n",
    "# 输入单词x是一个[1,vocab_size]大小的矩阵。当然实际上我们一般会用一批单词作为输入，那么就是[N, vocab_size]的矩阵了\n",
    "x = tf.placeholder(tf.float32, shape=(1,VOCAB_SIZE))\n",
    "# W1是一个[vocab_size, embedding_size]大小的矩阵\n",
    "W1 = tf.Variable(tf.random_normal([VOCAB_SIZE, EMBEDDING_SIZE]))\n",
    "# b1是一个[1，embedding_size]大小的矩阵\n",
    "b1 = tf.Variable(tf.random_normal([EMBEDDING_SIZE]))\n",
    "# 简单的矩阵乘法和加法\n",
    "hidden = tf.add(tf.mutmul(x,W1),b1)\n",
    "\n",
    "W2 = tf.Variable(tf.random_normal([EMBEDDING_SIZE,VOCAB_SIZE]))\n",
    "b2 = tf.Variable(tf.random_normal([VOCAB_SIZE]))\n",
    "# 输出是一个vocab_size大小的矩阵，每个值都是一个词的概率值\n",
    "prediction = tf.nn.softmax(tf.add(tf.mutmul(hidden,w2),b2))\n",
    "```\n",
    "\n",
    "### 损失函数\n",
    "网络定义好了，我们需要选一个损失函数来使用梯度下降算法优化模型。\n",
    "\n",
    "我们的输出层，实际上就是一个softmax分类器。所以按照常规套路，损失函数就选择**交叉熵损失函数**。\n",
    "\n",
    "哈哈，还记得交叉熵是啥吗？\n",
    "\n",
    "$$ H(p,q)=-\\sum_{x}p(x)logq(x)$$\n",
    "p,q是真是概率分布和估计概率分布。\n",
    "\n",
    "```python\n",
    "# 损失函数　\n",
    "cross_entropy_loss = tf.reduce_mean(-tf.reduce_sum(y_label * tf.log(prediction), reduction_indices=[1]))\n",
    "# 训练操作\n",
    "train_op = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy_loss)\n",
    "```\n",
    "接下来，就可以准备号数据，开始训练啦！\n",
    "\n",
    "\n",
    "### 为啥输入使用one-hot编码？\n",
    "\n",
    "我们知道word2vec训练后会得到一个权值矩阵W1(暂时忽略b1)，这个矩阵就是我们的所有词的向量表示啦！这个矩阵的每一行，就是一个词的矢量表示。如果两个矩阵相乘...\n",
    "![matrix_mult_w_one_hot](images/matrix_mult_w_one_hot.png)\n",
    "\n",
    "看到了吗？ont-hot编码的特点，**在矩阵相乘的时候，就是选取出矩阵中的某一行，而这一行就是我们输入这个词语的word2vec表示！**。\n",
    "\n",
    "怎么样？是不是很妙？\n",
    "\n",
    "由此，我们可以看出来，所谓的word2vec，实际上就是一个**查找表**，是一个**二维**的浮点数矩阵！\n",
    "\n",
    "以上是word2vec的skip-gram模型的完整分析，怎么样，是不是弄清楚了word2vec的原理和细节？\n",
    "\n",
    "完整代码请查看[word2vec](https://github.com/luozhouyang/machine-learning-notes/blob/master/word2vec/word2vec.py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
